package ase.eleitweg.server;

import ase.eleitweg.common.*;
import ase.eleitweg.common.fail.InconsistentDataException;

import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.util.*;

import org.apache.log4j.Logger;

import sun.util.logging.resources.logging;

import com.google.inject.*;

import ase.eleitweg.server.db.*;

public class RunController implements RunManager {
	
	//private ELeitwegDAO dao;
	
	@Inject ELeitwegDAO dao;
	@Inject WorkflowManager wm;
	private RunController() {
		Thread pinger = new Thread(new Pinger());
		pinger.start();
	}
	
	private Logger log = Logger.getLogger(RunController.class);
	
	private static RunController instance = null;
	
	private static RunManager remoteInstance = null;
	
	private List<ClientHandle> clients = new Vector<ClientHandle>();
		
	public static RunController getInstance() {
		if(instance==null) {
			instance = new RunController();
		}
		return instance;
	}
	
	public static RunManager getRemoteInstance() throws RemoteException {
		if(instance == null) {
			instance = new RunController();
		}
		if(remoteInstance == null) {
			remoteInstance = (RunManager) UnicastRemoteObject.exportObject((RunManager) instance, 0);
		}
		return remoteInstance;
	}

	public Run createRun(Workflow wf, User u) {
		return createRun(wf,u,null);
	}
	
	public Run createRun(Workflow wf, User u, String runDesc){
		Run i = new Run(wf, u, wf.getStartnode(), new Date(), true);
		if(runDesc!=null) i.setDesc(runDesc);
		int id = dao.createInstance(i);
		for(Run r : dao.getInstances(wf.getWNr())) {
			if(r.getId()==id) {
				return r;
			}
		}
		return null;
	}

	public void moveOn(Run i, Edge e) {
		i.setNode(e.getToNode());
		i.setAccepted(false);
		dao.updateInstance(i);
		
	}

	public void registerClient(ClientHandle ch) {
		synchronized(clients) {
			clients.add(ch);
		}
	}


	public void unregisterClient(ClientHandle ch) {
		synchronized (clients) {
			Iterator<ClientHandle> chIter = clients.iterator();
			while (chIter.hasNext()) {
				if (chIter.next().equals(ch)) {
					chIter.remove();
				}
			}
		}
	}
	
	private void notifyClients() {
		for(ClientHandle ch : clients) {
			try {
				ch.update();
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public List<Document> getDocuments(Run r) {
		return dao.getDocuments(r.getId());
	}

	public List<Run> getFinishedRuns() {
		List<Run> runs = dao.getInstances();
		List<Run> result = new Vector<Run>();
		for(Run r : runs) {
			if(r.isFinished()) {
				result.add(r);
			}
		}
		return result;
	}

	public List<Run> getMyRuns(User u) {
		List<Run> runs = dao.getInstances();
		List<Run> result = new Vector<Run>();
		for(Run r : runs) {
			if(r.isAccepted()) {
				if(r.getUser().getId() == u.getId()) {
					result.add(r);
				}
			}
		}
		return result;
	}
	
	public List<Run> getAcceptableRuns(User u) {
		List<Run> runs = dao.getInstances();
		List<Run> result = new Vector<Run>();
		for(Run r : runs) {
			if(!r.isAccepted()) {
				if(r.getNode().getGroup().isMember(u)) {
					result.add(r);
					r.setUser(User.getNullUser());
				}
			}
		}
		return result;
	}

	public List<Workflow> getStartableWF(User u) {
		List<Workflow> workflows;
		try {
			workflows = wm.getWorkflows();
		} catch (RemoteException e) {
			workflows = dao.getAllWorkflows();
		}
		List<Workflow> result = new Vector<Workflow>();
		for(Workflow wf : workflows) {
			if(wf.getStartnode().getGroup().isMember(u)) {
				result.add(wf);
			}
		}
		return result;
	}

	public List<Run> startedRun() {
		List<Run> runs = dao.getInstances();
		List<Run> result = new Vector<Run>();
		for(Run r : runs) {
			if(!r.isFinished()) {
				result.add(r);
				if(!r.isAccepted()) {
					r.setUser(User.getNullUser());
				}
			}
		}
		return result;
	}
	
	public void uploadDocuments(Run r, List<Document> docs) {
		List<Document> docsOld = getDocuments(r);
		dao.deleteDocumentsfromInstance(r.getId());
		for(Document d : docs) {
			for(Document docOld : docsOld) {
				if(d.getName().equalsIgnoreCase(docOld.getName())) {
					d.setVersion(docOld.getVersion()+1);
				}
			}
			dao.createDocument(d);
		}
	}
	
	public void acceptRun(Run r, User u) throws RemoteException, InconsistentDataException {
		if(!r.getNode().getGroup().isMember(u)) {
			throw new InconsistentDataException("User cannot accept this job because he is not member of incommig group.");
		}
		r.setUser(u);
		r.setAccepted(true);
		dao.updateInstance(r);
		notifyClients();
	}
	
	public List<Document> getHollowDocuments(int runId) {
		return dao.getDocumentWithoutData(runId);
	}
	
	private class Pinger implements Runnable {
		public void run() {
			while (true) {
				log.debug("PING");
				List<ClientHandle> clientsClone = new Vector<ClientHandle>();
				synchronized (clients) {
					clientsClone.addAll(clients);
				}
				Iterator<ClientHandle> chIter = clientsClone.iterator();
				while (chIter.hasNext()) {
					try {
						chIter.next().ping();
					} catch (Exception e) {
						log.debug("removing unreachable client from notification list");
						chIter.remove();
					}
				}
				try {
					Thread.currentThread().sleep(60000);
				} catch (InterruptedException e) {
					continue;
				}
			}
		}
	}
	
	public void unacceptRun(Run r) {
		r.setAccepted(false);
		dao.updateInstance(r);
		notifyClients();
	}
	
	public void removeRun(Run r) {
		dao.deleteDocumentsfromInstance(r.getId());
		dao.deleteInstance(r);
	}

}
